package engine.display;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;

/**
 * The Display class manages the game window.
 * 
 * @author Kyle_Solo
 */
public class Display {
	private int ascent;
	private transient BufferStrategy bs;
	private int cellHeight;
	private int cellWidth;
	private List<Displayable> displayableList;

	transient JFrame frame = new JFrame();
	/**
	 * The drawing surface.
	 */
	private transient Graphics2D graphics2D;

	/**
	 * The height in cells.
	 */
	private int height;
	private Input input;

	/**
	 * The height of the drawing surface. This is not always the same as the height of the Display!
	 */
	private int pxheight;

	/**
	 * The width of the drawing surface. This is not always the same as the width of the Display!
	 */
	private int pxwidth;

	/**
	 * The width in cells.
	 */
	private int width;
	/**
	 * The x-coordinate of the top-left corner of the drawing surface.
	 */
	public int xoff;
	/**
	 * The y-coordinate of the top-left corner of the drawing surface.
	 */
	public int yoff;

	/**
	 * Create a new Display using the default font (Courier New size 12)
	 * 
	 * @param hCharacterNumber
	 *            the number of characters horizontally
	 * @param vCharacterNumber
	 *            the number of characters vertically
	 */
	public Display(int hCharacterNumber, int vCharacterNumber) {
		frame.setFont(Font.decode("Courier New"));
		setupDisplay(hCharacterNumber, vCharacterNumber);
	}

	/**
	 * Create a new Display using the specified font.
	 * 
	 * @param hCharacterNumber
	 *            the number of characters horizontally
	 * @param vCharacterNumber
	 *            the number of characters vertically
	 * @param font
	 *            the font to use
	 */
	public Display(int hCharacterNumber, int vCharacterNumber, Font font) {
		frame.setFont(font);
		setupDisplay(hCharacterNumber, vCharacterNumber);
	}

	/**
	 * Create a new Display using the specified font.
	 * 
	 * @param hCharacterNumber
	 *            the number of characters horizontally
	 * @param vCharacterNumber
	 *            the number of characters vertically
	 * @param fontName
	 *            the font to use
	 */
	public Display(int hCharacterNumber, int vCharacterNumber, String fontName) {
		frame.setFont(Font.decode(fontName));
		setupDisplay(hCharacterNumber, vCharacterNumber);
	}

	/**
	 * Registers a Displayable object with this display. That object will receive render events from
	 * this display.
	 * 
	 * @param d
	 *            the Displayable object to add to this display
	 */
	public void add(Displayable d) {
		if (!displayableList.contains(d)) {
			displayableList.add(d);
		}
	}

	/**
	 * Clears the screen.
	 */
	public synchronized void cls() {
		graphics2D.setColor(Color.black);
		graphics2D.fillRect(0, 0, pxwidth, pxheight);
	}

	/**
	 * Clears the specified position and draws over it with a color
	 * 
	 * @param x
	 *            the x-coordinate in cells
	 * @param y
	 *            the y-coordinate in cells
	 * @param color
	 *            the color to render
	 */
	public void drawBackground(int x, int y, Color color) {
		graphics2D.setColor(color);
		graphics2D.fillRect(cellWidth * x, cellHeight * y, cellWidth, cellHeight);
	}

	/**
	 * Draws a single character. Does not fill in the background.
	 * 
	 * @param x
	 *            the x-coordinate in cells
	 * @param y
	 *            the y-coordinate in cells
	 * @param character
	 *            the character to render
	 * @param color
	 *            the color of that character
	 */
	public void drawCharacter(int x, int y, String character, Color color) {
		graphics2D.setColor(color);
		graphics2D.drawString(character, cellWidth * x, cellHeight * y + ascent);
	}

	/**
	 * Draws a string, filling in the background.
	 * 
	 * @param x
	 *            the x-coordinate in cells
	 * @param y
	 *            the y-coordinate in cells
	 * @param string
	 *            the string to draw.
	 * @param color
	 *            the color of the string
	 * @param background
	 *            the color of the background
	 */
	public void drawString(int x, int y, String string, Color color, Color background) {
		for (int i = 0; i < string.length(); i++) {
			drawBackground(x + i, y, background);
			drawCharacter(x + i, y, string.substring(i, i + 1), color);
		}
	}

	/**
	 * @return the height of a single cell in pixels
	 */
	public int getCellHeight() {
		return cellHeight;
	}

	/**
	 * @return the width of a single cell in pixels
	 */
	public int getCellWidth() {
		return cellWidth;
	}

	/**
	 * @return the height in cells
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * @return the input object associated with this display
	 */
	public Input getInput() {
		return input;
	}

	/**
	 * @return the width in cells
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * Unregisters a Displayable object from this display. That object will no longer receive render
	 * events from this display.
	 * 
	 * @param d
	 *            the Displayable object to remove from this display
	 */
	public void remove(Displayable d) {
		if (displayableList.contains(d)) {
			displayableList.remove(d);
		}
	}

	/**
	 * This method draws the canvas to the screen.
	 */
	public synchronized void render() {
		// Render single frame
		do {
			do {
				// get canvas
				graphics2D = (Graphics2D) bs.getDrawGraphics();
				graphics2D.translate(xoff, yoff);
				graphics2D.clipRect(0, 0, pxwidth, pxheight);

				// clear the drawing canvas
				cls();

				// draw things to canvas
				for (Displayable d : displayableList) {
					d.render();
				}

				// draw the input (debugging)
				input.render(graphics2D);

				// dispose of canvas
				graphics2D.dispose();
			} while (bs.contentsRestored());

			// Display the buffer
			bs.show();
			Toolkit.getDefaultToolkit().sync();
			// Repeat the rendering if the drawing buffer was lost
		} while (bs.contentsLost());

	}

	private void setupDisplay(int hCharacterNumber, int vCharacterNumber) {
		frame.setVisible(true);
		FontMetrics fm = frame.getGraphics().getFontMetrics();
		cellWidth = fm.getMaxAdvance();
		cellHeight = fm.getHeight();
		ascent = fm.getMaxAscent();
		pxwidth = cellWidth * hCharacterNumber;
		pxheight = cellHeight * vCharacterNumber;
		width = hCharacterNumber;
		height = vCharacterNumber;

		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		Insets inset = frame.getInsets();
		xoff = inset.left;
		yoff = inset.top;
		frame.setSize(inset.left + inset.right + pxwidth, inset.top + inset.bottom + pxheight);
		frame.setLocationRelativeTo(null);
		frame.createBufferStrategy(2);
		bs = frame.getBufferStrategy();
		graphics2D = (Graphics2D) bs.getDrawGraphics();

		displayableList = new ArrayList<Displayable>();
		input = new Input(this);
	}
}
